package com.szwistar.common.comm;

import java.net.InetSocketAddress;
import java.net.SocketAddress;

import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.serial.SerialAddress;
import org.apache.mina.transport.serial.SerialAddress.DataBits;
import org.apache.mina.transport.serial.SerialAddress.FlowControl;
import org.apache.mina.transport.serial.SerialAddress.Parity;
import org.apache.mina.transport.serial.SerialAddress.StopBits;
import org.apache.mina.transport.serial.SerialConnector;
import org.apache.mina.transport.socket.nio.NioDatagramConnector;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 点对点（单连接）客户端
 */
public class P2pBase<T extends CommEndpoint> implements IoHandler {
	protected static Logger LOG = LoggerFactory.getLogger("Default");

	protected SocketAddress local;
	protected SocketAddress remote;
	protected ProtocolCodecFilter codecFilter;
	protected IoConnector connector;
	protected IoSession session;

	protected Class<T> clientClazz;
	protected T client;

	// ANY
	public P2pBase(SocketAddress local, SocketAddress remote, Class<T> clientClazz, ProtocolCodecFilter codecFilter, IoConnector connector) {
		this.local = local;
		this.remote = remote;
		this.clientClazz = clientClazz;
		this.connector = connector;
		this.codecFilter = codecFilter;
	}

	// ANY
	public P2pBase(SocketAddress remote, Class<T> clientClazz, ProtocolCodecFilter codecFilter, IoConnector connector) {
		this(null, remote, clientClazz, codecFilter, connector);
	}

	// TCP/UDP
	public P2pBase(InetSocketAddress address, Class<T> clientClazz, ProtocolCodecFilter codecFilter, boolean tcp) {
		this(address, clientClazz, codecFilter, tcp?(new NioSocketConnector()):(new NioDatagramConnector()));
	}

	// TCP/UDP
	public P2pBase(String host, int port, Class<T> clientClazz, ProtocolCodecFilter codecFilter, boolean tcp) {
		this(new InetSocketAddress(host, port), clientClazz, codecFilter, tcp?(new NioSocketConnector()):(new NioDatagramConnector()));
	}

	// 串口
	public P2pBase(SerialAddress address, Class<T> clientClazz, ProtocolCodecFilter codecFilter) {
		this(address, clientClazz, codecFilter, new SerialConnector());
	}

	// 串口
	public P2pBase(String name, int bauds, DataBits dataBits, StopBits stopBits, Parity parity, FlowControl flowControl, Class<T> clientClazz, ProtocolCodecFilter codecFilter) {
		this(new SerialAddress(name, bauds, dataBits, stopBits, parity, flowControl), clientClazz,  codecFilter);
	}

	@Override
	public String toString() {
		return String.format("P2P连接(%s@%s)", clientClazz.getSimpleName(), remote);
	}

	public IoConnector getConnector() {
		return connector;
	}

	/**
	 * 打开连接
	 * @return
	 * @throws Exception
	 */
	public T open(Object context) throws Exception {
		connector.setHandler(this);

		if(codecFilter != null) {
			// 配置数据处理协议栈
			connector.getFilterChain().addLast("codec", codecFilter);
		}

		// 开始连接
        LOG.debug("打开连接： {} ...", this);
		ConnectFuture future = connector.connect(remote);

		// 等待连接完成
		try {
			future.await();
			session = future.getSession();
		} catch(Exception e) {
			// 如果连接失败，必须要主动关闭本地连接器
			connector.dispose(false);
			throw e;
		}

		if(clientClazz != null) {
			// 创建客户端对象
			client = clientClazz.newInstance();

			// 触发事件
			client.onCreate(session, context);
			client.onOpened();
		}

        return client;
	}

	/**
	 * 关闭连接
	 */
	public void close() {
		if(session != null) {
			session.close(true);
			session = null;
		}

		if(connector != null) {
			connector.dispose();
			connector = null;
		}
	}

	@Override
	public void sessionCreated(IoSession session) {
	}

	@Override
	public void sessionOpened(IoSession session) {
		this.session = session;
		LOG.debug("{} 连接已建立，{}(本地) <-> (远程){}",
				this.getClass().getSimpleName(),
				session.getLocalAddress(),
				session.getRemoteAddress());
	}

	@Override
	public void sessionClosed(IoSession session) throws Exception {
		client.onClosed();
		this.session = null;
	}

	@Override
	public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
    	client.onIdle(status);
	}

	@Override
	public void messageReceived(IoSession session, Object message) throws Exception {
		client.onMessageReceived(message);
	}

	@Override
	public void messageSent(IoSession session, Object message) throws Exception {
		client.onMessageSent(message);
	}

	@Override
	public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
		client.onExceptionCaught(cause);
	}
}
